#include "ClientSideSynchronizer.h"
#include <assert.h>
#include <iostream>
#include "game-printer.h"

#define CLIENT_TIMEOUT_SEND 70000
#define SIMPLE_SYNCHRO

ClientSideSynchronizer * ClientSideSynchronizer::instance= NULL;
PlayerMoves * ClientSideSynchronizer::deserializePlayerMoves(
							     std::string & dataString)
{

	return NULL;
}

ClientSideSynchronizer::ClientSideSynchronizer()
{

	connected = false;
	already_in_game = false;
	player_number = 1;
	local_round_number = 0;
}

bool ClientSideSynchronizer::connectToServer()
{
	c.client_config("udp-client/options.cfg");
	c.submit_player();
	if ( true == c.connect_to_game() )
	{
		connected = true;
		return true;
	}
	else
	{
		return false;
	}
}
ClientSideSynchronizer * ClientSideSynchronizer::getInstance()
{
	if (instance == NULL)
	{
		instance = new ClientSideSynchronizer();
	}
	return instance;
}

void ClientSideSynchronizer::putPlayerOnTheMap(PlayerMoves * p_moves)
{
	int local_round_number = MyMap::instanceOf()->getRoundNumber();
	SnakePlayer * virtual_snake_player = MyMap::instanceOf()->getSnakePlayerByNumber(p_moves->player_number);

	assert(virtual_snake_player);

	if (p_moves->remote_round_number == local_round_number)
	{
		/* normal example - number is correct - only fill number*/
		virtual_snake_player->moveTo(p_moves->last_directions[PLAYER_LAST_DIRECTION_NUMBER]);
	}
	else if (p_moves->remote_round_number - local_round_number
			> PLAYER_MAX_MOVES_NUMBER)
	{
		/* difference is bigger than moves amount we can recover - EXITING*/
		assert( local_round_number - p_moves->remote_round_number < PLAYER_MAX_MOVES_NUMBER );
	}
	else
	{
		/* at first we need to count difference between turns*/
		int round_difference = p_moves->remote_round_number
				- local_round_number;
		assert(round_difference < PLAYER_MAX_MOVES_NUMBER);

		/* recovering*/
		int debug_last_iter = 0;//need to be 0 at end of for loop
		for (int i = round_difference; i >= 0; --i)
		{
			/* moving virtual player - need to make moves to be in current state*/

			virtual_snake_player->moveTo(p_moves->last_directions[PLAYER_LAST_DIRECTION_NUMBER]);
			debug_last_iter = i;
		}

		assert(0 == debug_last_iter);
	}

}

void ClientSideSynchronizer::rollbackSnakeAndRecover(int player_number)
{
	SnakePlayer * sp_temp= NULL;
	int remote_round = c.shared_serv_mem.server_round_number;
	sp_temp = MyMap::getInstance()->getSnakePlayerByNumber(player_number);
	PlayerMoves * p_moves =& c.shared_serv_mem.player_moves[player_number];
	
	int amount_round_back = 0;
/*
	if (remote_round > PLAYER_MAX_MOVES_NUMBER)
	{
		amount_round_back = PLAYER_MAX_MOVES_NUMBER;
	}
	else
	{
		amount_round_back = remote_round;
	}
*/
	sp_temp->backRevisionNum(amount_round_back);
	for ( int i = amount_round_back; i >= 0; --i)
	{
		/* moving virtual player - need to make moves to be in current state*/

		sp_temp->moveTo(p_moves->last_directions[i]);
		
	}

}
void ClientSideSynchronizer::mergePositions()
{

	PlayerMoves * p_moves= NULL;
	SnakePlayer * sp_temp= NULL;

	for (int i=0; i < c.shared_serv_mem.current_players_number; ++i)
	{
		sp_temp = MyMap::getInstance()->getSnakePlayerByNumber(i);
		p_moves =&c.shared_serv_mem.player_moves[i];
		assert(sp_temp != NULL);
		if (sp_temp->getHeadSegment()->x != p_moves->actual_x
			|| sp_temp->getHeadSegment()->y != p_moves->actual_y	)
		{
			//cout << "X changed!!!!" <<endl;
			MyMap::getInstance()->setMapCharAt(MyMap::empty_field,
					sp_temp->getHeadSegment()->x,
					sp_temp->getHeadSegment()->y);
			sp_temp->setHeadSegmentCoords(p_moves->actual_x,
					p_moves->actual_y);
			
			//rollbackSnakeAndRecover(i);
		}

	}

}
void ClientSideSynchronizer::resolvePlayerData()
{

	assert( true == connected);

	if ( true == connected)
	{

		SharedMemStruct shared_temp;
		c.receive((char*)&shared_temp, sizeof(SharedMemStruct));

		//c.receive((char*)&c.shared_serv_mem, sizeof(SharedMemStruct));
		int server_status = shared_temp.game_started;
		int remote_round =shared_temp.server_round_number;
		
		c.shared_serv_mem = shared_temp;


		switch (server_status)
		{
		case STATE_GAME_STOP:
			MyMap::instanceOf()->resetMapAndSnakes();
			MyMap::instanceOf()->markAllChanged();
			local_round_number = 0;

			if (getAppData() != NULL)
			{
				if (getPlayerWinLostStatus(player_number)
						== PLAYER_STATE_GAME_LOST)
				{
					gp_inform_on_middle(getAppData(), 
					STRING_YOU_LOST);

				}
				else if (getPlayerWinLostStatus(player_number)
						== PLAYER_STATE_GAME_WIN)
				{
					gp_inform_on_middle(getAppData(), 
					STRING_YOU_WIN);

				}
				waitToStart();
			}
			break;
		}

	}

}
/* implementing from PlayerSynchronizer*/
void ClientSideSynchronizer::synchronize()
{
	//MyMap::instanceOf()->cleanupLastChanges();
	MyMap::instanceOf()->markAllChanged();
	SnakePlayer * sp_temp= NULL;
	//cout << "SYNCHRONIZE " <<endl;
	for (int i=0; i < c.shared_serv_mem.current_players_number; ++i)
	{
#ifndef SIMPLE_SYNCHRO			
		/* moving only remote players */
		if (i != player_number)
		{
#endif 	
		sp_temp = MyMap::getInstance()->getSnakePlayerByNumber(i);
		assert(sp_temp != NULL);
		if (c.shared_serv_mem.game_started != STATE_GAME_STOP)
		{
			sp_temp->moveTo(c.shared_serv_mem.player_moves[i].last_directions[0]);
		}
#ifndef SIMPLE_SYNCHRO		
	}
#endif		
	}
	mergePositions();
	
			/* cleaning last changes - only changed fields will be redrawn*/
	
}

int ClientSideSynchronizer::getPlayerWinLostStatus(int number)
{
	return c.shared_serv_mem.player_moves[number].active_state;
}
void ClientSideSynchronizer::waitToStart()
{
	SnakePlayer * sp= NULL;
	while (1)
	{
		/*printf("Incoming transmission"); */
		c.receive((char*)&c.shared_serv_mem, sizeof(SharedMemStruct));

		if ( STATE_GAME_STARTED == c.shared_serv_mem.game_started)
		{

			if (already_in_game == false)
			{
				for (int i =0; i
						< c.shared_serv_mem.current_players_number; ++i)
				{
					assert( c.shared_serv_mem.current_players_number == 2);
					assert( c.shared_serv_mem.player_moves[i].actual_x >= 0);
					cout << "Player move "<< i <<endl;
					cout << "Actual x:"
							<< c.shared_serv_mem.player_moves[i].actual_x
							<<endl;
					cout << "Actual y:"
							<< c.shared_serv_mem.player_moves[i].actual_y
							<< endl <<endl;

					sp
							= new SnakePlayer(i, c.shared_serv_mem.player_moves[i].actual_x,
									c.shared_serv_mem.player_moves[i].actual_y);
				}
			}
			already_in_game = true;
			break;

		}
		else
		{
			//printf("Gra NIE wystartowala\n");
			//printf("state##%d\n", c.shared_serv_mem.game_started);
		}
	}

}

void ClientSideSynchronizer::startThreads()
{
	pthread_t sender_thread, resolver_thread, game_thread;

	int iret1, iret2, iret3;

	/* Create independent threads each of which will execute function */

	iret1 = pthread_create( &sender_thread, NULL, client_sender_func,
			(void*) ClientSideSynchronizer::getInstance() );
	iret2 = pthread_create( &resolver_thread, NULL, client_resolver_func,
			(void*) ClientSideSynchronizer::getInstance());

	iret3 = pthread_create( &game_thread, NULL, client_game_func,
			(void*) ClientSideSynchronizer::getInstance());
	/* Wait till threads are complete before main continues. Unless we  */
	/* wait we run the risk of executing an exit which will terminate   */
	/* the process and all threads before the threads have completed.   */

	pthread_join(sender_thread, NULL);
	pthread_join(resolver_thread, NULL);
	pthread_join(game_thread, NULL);

	printf("Thread 1 returns: %d\n", iret1);
	printf("Thread 2 returns: %d\n", iret2);
	exit(0);
}
void ClientSideSynchronizer::sendLastDirection()
{

}
ClientSideSynchronizer::~ClientSideSynchronizer()
{

}

/*************************** ###THREADS DEFINITIONS ### ***************************/

void *client_game_func(void *ptr)
{
	ClientSideSynchronizer* synchronizer = (ClientSideSynchronizer *) ptr;

	assert(synchronizer->getAppData());
	//cout << "GAME FUNC INITIALIZED" <<endl;
	gp_do_test_gaming(synchronizer->getAppData());
}

void *client_resolver_func(void *ptr)
{

	ClientSideSynchronizer * synchronizer = (ClientSideSynchronizer *) ptr;

	while (1)
	{

		synchronizer->resolvePlayerData();
#ifdef SIMPLE_SYNCHRO		
		synchronizer->synchronize();
#endif		
	}
}

void *client_sender_func(void *ptr)
{

	ClientSideSynchronizer * synchronizer = (ClientSideSynchronizer *) ptr;
	ClientComunicationStruct c_com_str;
	UdpClient c = synchronizer->getUdpClient();
	strcpy(c_com_str.nick, c.client_options.nick);

	while (1)
	{
		c.send((char*)&c_com_str, sizeof(c_com_str));
		c_com_str.d =synchronizer->getLastDirection();
		//synchronizer->sendToAllPlayers();
		/* sending time simulation */
		usleep( CLIENT_TIMEOUT_SEND /3);
		//synchronizer->sendToAllPlayers();
		//}
		//synchronizer->waitForNewPackage();
	}

}

